﻿using Microsoft.Playwright;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Globalization;

namespace ssp
{
    public class Spride
    {
        // --- 数据结构定义 ---
        #region 数据结构
        public class AuctionItem
        {
            [JsonPropertyName("itemId")]
            public string ItemId { get; set; }
            [JsonPropertyName("title")]
            public string Title { get; set; }
            [JsonPropertyName("url")]
            public string Url { get; set; }
            [JsonPropertyName("currentPrice")]
            public decimal CurrentPrice { get; set; }
            [JsonPropertyName("consultPrice")]
            public decimal ConsultPrice { get; set; }
            [JsonPropertyName("status")]
            public string Status { get; set; }
            [JsonPropertyName("start")]
            public DateTime Start { get; set; }
            [JsonPropertyName("end")]
            public DateTime End { get; set; }
            [JsonPropertyName("viewerCount")]
            public int ViewerCount { get; set; }
            [JsonPropertyName("applyCount")]
            public int ApplyCount { get; set; }
            [JsonPropertyName("picUrl")]
            public string PicUrl { get; set; }
        }

        public class RawAuctionData
        {
            [JsonPropertyName("data")]
            public List<RawAuctionItem> Data { get; set; }
        }
        public class RawAuctionItem
        {
            [JsonPropertyName("id")] public long Id { get; set; }
            [JsonPropertyName("title")] public string Title { get; set; }
            [JsonPropertyName("itemUrl")] public string ItemUrl { get; set; }
            [JsonPropertyName("status")] public string Status { get; set; }
            [JsonPropertyName("currentPrice")] public decimal CurrentPrice { get; set; }
            [JsonPropertyName("consultPrice")] public decimal ConsultPrice { get; set; }
            [JsonPropertyName("start")] public long Start { get; set; }
            [JsonPropertyName("end")] public long End { get; set; }
            [JsonPropertyName("viewerCount")] public int ViewerCount { get; set; }
            [JsonPropertyName("applyCount")] public int ApplyCount { get; set; }
            [JsonPropertyName("picUrl")] public string PicUrl { get; set; }
        }
        public class CookieJson
        {
            [JsonPropertyName("domain")] public string Domain { get; set; }
            [JsonPropertyName("name")] public string Name { get; set; }
            [JsonPropertyName("value")] public string Value { get; set; }
            [JsonPropertyName("path")] public string Path { get; set; }
            [JsonPropertyName("httpOnly")] public bool HttpOnly { get; set; }
            [JsonPropertyName("secure")] public bool Secure { get; set; }
            [JsonPropertyName("session")] public bool Session { get; set; }
            [JsonPropertyName("expirationDate")] public double? ExpirationDate { get; set; }
            [JsonPropertyName("sameSite")] public object SameSite { get; set; }
        }

        // Gitee API 请求体 (用于创建 Issue)
        public class GiteeIssueRequest
        {
            [JsonPropertyName("access_token")]
            public string AccessToken { get; set; }
            [JsonPropertyName("title")]
            public string Title { get; set; }
            [JsonPropertyName("body")]
            public string Body { get; set; }
            // 根据您的 TS 示例，Gitee API 似乎允许在 /repos/{owner}/issues 端点
            // 的 body 中指定 repo，我们遵循这个逻辑
            [JsonPropertyName("repo")]
            public string Repo { get; set; }
        }

        // Gitee API 请求体 (用于发表评论)
        public class GiteeCommentRequest
        {
            [JsonPropertyName("access_token")]
            public string AccessToken { get; set; }
            [JsonPropertyName("body")]
            public string Body { get; set; }
        }

        // Gitee API 响应体 (用于搜索和创建)
        public class GiteeIssueResponse
        {
            [JsonPropertyName("id")] public long Id { get; set; }
            [JsonPropertyName("number")] public string Number { get; set; }
            [JsonPropertyName("title")] public string Title { get; set; }
            [JsonPropertyName("html_url")] public string HtmlUrl { get; set; }
        }
        #endregion

        const string GITEE_TOKEN = "3e50f1c5d989cf8adb2aa94402b60e80";
        const string GITEE_OWNER = "baymin_gitee";   // 例如: "zhangsan"
        const string GITEE_REPO = "nothing";   // 例如: "paimai-data"

        const string TARGET_URL = "https://sf.taobao.com/list/0__1.htm?location_code=330212&auction_source=0&st_param=4&auction_start_seg=-1&q=%BA%A3%B4%B4%BC%D2%D4%B0";
        const string WORKER_URL = "https://paimai-worker.your-username.workers.dev/write";
        const string WORKER_SECRET_KEY = "YOUR_SUPER_SECRET_KEY";
        const string COOKIE_FILE_PATH = "cookies.json"; // Cookie 文件名，将保存在 bin/Debug/

        private static readonly HttpClient GiteeClient = new HttpClient();
        // ---


        public static async Task<string> RunAsync()
        {
            var log = new StringBuilder();
            log.AppendLine("--- 开始执行法拍抓取任务 ---");

            GiteeClient.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft-Playwright-Scraper/1.0");

            Tuple<List<AuctionItem>, string> scrapeResult = await ScrapeDataAsync(TARGET_URL).ConfigureAwait(false);
            log.Append(scrapeResult.Item2);

            if (scrapeResult.Item1 != null && scrapeResult.Item1.Count > 0)
            {
                log.AppendLine($"--- 抓取到 {scrapeResult.Item1.Count} 条数据，开始同步到 Gitee Issues ---");
                foreach (var item in scrapeResult.Item1)
                {
                    string issueLog = await SyncItemToGiteeAsync(item).ConfigureAwait(false);
                    log.AppendLine(issueLog);
                    await Task.Delay(250).ConfigureAwait(false);
                }
                log.AppendLine("--- Gitee 同步完毕 ---");
                _ = SyncItemToGiteeAsync(new AuctionItem()
                {
                    ItemId = $"一共查询到: [{scrapeResult.Item1.Count}]个拍卖" + DateTime.Now.ToString("yyyy-MM-dd HH:mm"),
                });
            }
            else
            {
                log.AppendLine("没有抓取到数据，跳过发送。");
            }

            return log.ToString();
        }

        /// <summary>
        /// 核心逻辑：为每个拍品同步 Gitee Issue，数据作为评论添加
        /// </summary>
        static async Task<string> SyncItemToGiteeAsync(AuctionItem item)
        {
            string issueTitlePrefix = $"[法拍][{item.ItemId}]";
            string issueTitle = $"{issueTitlePrefix} {item.Title}";
            string logPrefix = $"拍品 {item.ItemId}:";

            try
            {
                // 1. 搜索 Issue
                var existingIssue = await SearchGiteeIssueAsync(item.ItemId).ConfigureAwait(false);
                string issueNumber;

                if (existingIssue != null)
                {
                    // 2.A 如果 Issue 存在
                    issueNumber = existingIssue.Number;
                    logPrefix += $" [存在 Issue #{issueNumber}]";
                    return logPrefix;
                }
                else
                {
                    // 2.B 如果 Issue 不存在，创建它
                    logPrefix += " [Issue 不存在]";
                    var newIssue = await CreateGiteeIssueAsync(issueTitle, item).ConfigureAwait(false);
                    if (newIssue != null)
                    {
                        issueNumber = newIssue.Number;
                        logPrefix += $" [已创建 Issue #{issueNumber}]";
                        // 3. 将拍品数据作为“评论”添加到 Issue
                        string commentBody = FormatItemAsMarkdownComment(item);
                        await AddCommentToGiteeIssueAsync(issueNumber, commentBody).ConfigureAwait(false);
                    }
                    else
                    {
                        throw new Exception("创建 Issue 失败，未返回 Issue 编号。");
                    }
                }

                return $"{logPrefix} [评论成功] 已添加最新数据评论。";
            }
            catch (Exception ex)
            {
                return $"{logPrefix} [异常] 同步失败: {ex.Message}";
            }
        }

        /// <summary>
        /// 搜索 Gitee Issue
        /// </summary>
        static async Task<GiteeIssueResponse> SearchGiteeIssueAsync(string titlePrefix)
        {
            // 修正：使用 Gitee API v5 的标准 repo issue 列表端点
            // ⬇️ ⬇️ ⬇️ 关键修正：
            // 1. 使用 /search/issues 端点
            // 2. 搜索关键字是 'q'
            // 3. 使用 'repo' 参数来限定仓库
            // 4. 添加 'type=issue' 参数
            // ⬇️ ⬇️ ⬇️ 关键修正：
            // 1. 删除了无效的 'state=all' 参数。
            // 2. Gitee API 将默认搜索 state=open 的 Issue，这正是我们想要的。
            string searchUrl = $"https://gitee.com/api/v5/search/issues?access_token={GITEE_TOKEN}&state=open&type=issue&q={Uri.EscapeDataString(titlePrefix)}&repo={GITEE_OWNER}/{GITEE_REPO}";
            // ⬆️ ⬆️ ⬆️
            var response = await GiteeClient.GetAsync(searchUrl).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                var errorBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                throw new Exception($"Gitee API 搜索失败: {response.StatusCode} - {errorBody}");
            }

            var issues = await response.Content.ReadFromJsonAsync<List<GiteeIssueResponse>>().ConfigureAwait(false);

            // 在返回的结果中精确查找
            return issues.FirstOrDefault(issue => issue.Title.Contains(titlePrefix));
        }

        /// <summary>
        /// 创建一个新的 Gitee Issue (仅包含标题和基本信息)
        /// </summary>
        static async Task<GiteeIssueResponse> CreateGiteeIssueAsync(string issueTitle, AuctionItem item)
        {
            // 修正：使用 Gitee API v5 的标准 create repo issue 端点
            string createUrl = $"https://gitee.com/api/v5/repos/{GITEE_OWNER}/issues";

            // 正文只包含基本信息
            string issueBody = $"自动创建的法拍跟踪 Issue。\n\n" +
                               $"**拍品ID:** {item.ItemId}\n" +
                               $"**原始链接:** {item.Url}\n" + FormatItemAsMarkdownComment(item, true);

            var requestBody = new GiteeIssueRequest
            {
                AccessToken = GITEE_TOKEN,
                Title = issueTitle,
                Body = issueBody,
                Repo = GITEE_REPO // 按照您的 TS 示例，我们在 body 中也传递 repo
            };

            var response = await GiteeClient.PostAsJsonAsync(createUrl, requestBody).ConfigureAwait(false);

            var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            if (!response.IsSuccessStatusCode)
            {
                throw new Exception($"Gitee API 创建 Issue 失败: {response.StatusCode} - {responseString}");
            }

            return JsonSerializer.Deserialize<GiteeIssueResponse>(responseString);
        }

        /// <summary>
        /// 向指定 Issue 发表评论 (包含详细数据)
        /// </summary>
        static async Task AddCommentToGiteeIssueAsync(string issueNumber, string commentBody)
        {
            // 修正：使用 Gitee API v5 的标准 create comment 端点
            string commentUrl = $"https://gitee.com/api/v5/repos/{GITEE_OWNER}/{GITEE_REPO}/issues/{issueNumber}/comments";

            var requestBody = new GiteeCommentRequest
            {
                AccessToken = GITEE_TOKEN,
                Body = commentBody
            };

            var response = await GiteeClient.PostAsJsonAsync(commentUrl, requestBody).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                throw new Exception($"Gitee API 发表评论失败: {response.StatusCode} - {responseString}");
            }
        }

        /// <summary>
        /// 辅助函数：将拍品数据格式化为 Markdown 评论
        /// </summary>
        private static string FormatItemAsMarkdownComment(AuctionItem item, bool table=false)
        {
            var culture = new CultureInfo("zh-CN"); // 用于格式化货币
            var mdBody = new StringBuilder();
            if (table)
            {
                mdBody.AppendLine($"\n### 拍品状态更新：{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}\n");
                mdBody.AppendLine();
                mdBody.AppendLine($"![拍品图片]({item.PicUrl})");
                mdBody.AppendLine();
                mdBody.AppendLine("| 属性 | 详情 |");
                mdBody.AppendLine("| --- | --- |");
                mdBody.AppendLine($"| 状态 | **{item.Status}** |");
                mdBody.AppendLine($"| 当前价 | **¥ {item.CurrentPrice.ToString("N0", culture)}** |");
                mdBody.AppendLine($"| 评估价 | ¥ {item.ConsultPrice.ToString("N0", culture)} |");
                mdBody.AppendLine($"| 围观/报名 | {item.ViewerCount} / {item.ApplyCount} |");
                mdBody.AppendLine($"| 开拍时间 | {item.Start:yyyy-MM-dd HH:mm} |");
                mdBody.AppendLine($"| 结束时间 | {item.End:yyyy-MM-dd HH:mm} |");
            }
            else
            {
                mdBody.AppendLine(); // 空行
                mdBody.AppendLine($"  >");
                // 1. 状态更新时间
                mdBody.AppendLine($"- **拍品状态更新**: **{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}**");
                mdBody.AppendLine(); // 空行
                // 4. 详细信息（使用列表）
                mdBody.AppendLine($"- **状态**: **{item.Status}**");
                mdBody.AppendLine($"- **当前价**: **¥ {item.CurrentPrice.ToString("N0", culture)}**");
                mdBody.AppendLine($"- **评估价**: ¥ {item.ConsultPrice.ToString("N0", culture)}");
                mdBody.AppendLine($"- **围观/报名**: {item.ViewerCount} / {item.ApplyCount}");
                mdBody.AppendLine($"- **开拍时间**: {item.Start:yyyy-MM-dd HH:mm}");
                mdBody.AppendLine($"- **结束时间**: {item.End:yyyy-MM-dd HH:mm}");
            }
            return mdBody.ToString();
        }


        // 核心抓取函数 (不变)
        // ⬇️ ⬇️ ⬇️ 
        // ** 核心抓取函数 (已按您的要求更新) **
        // ⬇️ ⬇️ ⬇️
        static async Task<Tuple<List<AuctionItem>, string>> ScrapeDataAsync(string url)
        {
            var log = new StringBuilder();
            log.AppendLine("启动 Playwright...");

            using (var playwright = await Playwright.CreateAsync().ConfigureAwait(false))
            {
                log.AppendLine("启动浏览器...");

                IBrowser browser = null;
                IBrowserContext context = null; // 把 context 提到外面
                IPage page = null;

                try
                {
                    browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
                    {
                        // ⬇️ 关键：必须为 false，这样您才能在卡住时手动验证
                        Headless = false
                    }).ConfigureAwait(false);

                    context = await browser.NewContextAsync(new BrowserNewContextOptions
                    {
                        UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"
                    }).ConfigureAwait(false);


                    // ⬇️ 关键步骤 1: 尝试加载上次保存的 Cookies ⬇️
                    try
                    {
                        log.AppendLine($"正在读取 {COOKIE_FILE_PATH} 文件...");
                        if (File.Exists(COOKIE_FILE_PATH))
                        {
                            string cookieJsonString = File.ReadAllText(COOKIE_FILE_PATH);
                            // 反序列化为 Playwright 的 Cookie 对象
                            var cookies = JsonSerializer.Deserialize<List<CookieJson>>(cookieJsonString);

                            // 转换
                            /**/
                            var cookieParams = cookies.Select(c => new Cookie
                            {
                                Name = c.Name,
                                Value = c.Value,
                                Domain = c.Domain,
                                Path = c.Path,
                                HttpOnly = c.HttpOnly,
                                Secure = c.Secure,
                                SameSite = ParseSameSite(c.SameSite.ToString()), // ⬅️ **修正！**
                                Expires = c.ExpirationDate.HasValue ? (float?)c.ExpirationDate.Value : null
                            }).ToList();

                            await context.AddCookiesAsync(cookieParams).ConfigureAwait(false);
                            log.AppendLine($"成功加载 {cookieParams.Count} 条 Cookies。");
                        }
                        else
                        {
                            log.AppendLine($"未找到 {COOKIE_FILE_PATH} 文件，将进行首次抓取。");
                        }
                    }
                    catch (Exception ex)
                    {
                        log.AppendLine($"加载 Cookies 失败: {ex.Message}");
                        log.AppendLine("警告：将尝试在未登录状态下抓取。");
                    }


                    await context.AddInitScriptAsync(
                        "Object.defineProperty(navigator, 'webdriver', { get: () => undefined });"
                    ).ConfigureAwait(false);


                    page = await context.NewPageAsync().ConfigureAwait(false);

                    try
                    {
                        log.AppendLine($"导航到: {url}");
                        await page.GotoAsync(url, new PageGotoOptions { WaitUntil = WaitUntilState.DOMContentLoaded, Timeout = 60000 }).ConfigureAwait(false);

                        log.AppendLine("页面加载完成... ");
                        log.AppendLine("【！！！】程序将等待最多 5 分钟，如果出现'拖动滚动条'验证，请手动完成。");

                        var dataSelector = "#sf-item-list-data";

                        await page.WaitForSelectorAsync(dataSelector, new PageWaitForSelectorOptions
                        {
                            State = WaitForSelectorState.Attached,
                            // 延长超时到 5 分钟 (300000ms)，给您足够的时间手动验证
                            Timeout = 300000
                        }).ConfigureAwait(false);


                        log.AppendLine("验证成功！数据脚本已找到，正在提取 JSON...");
                        var jsonDataString = await page.EvaluateAsync<string>("selector => document.querySelector(selector).textContent", dataSelector).ConfigureAwait(false);

                        if (string.IsNullOrEmpty(jsonDataString))
                        {
                            throw new Exception("未能找到 ID 为 'sf-item-list-data' 的 <script> 标签");
                        }

                        var rawData = JsonSerializer.Deserialize<RawAuctionData>(jsonDataString);
                        if (rawData?.Data == null)
                        {
                            throw new Exception("JSON 数据格式不正确，未找到 'data' 数组");
                        }

                        var items = rawData.Data.Select(item => new AuctionItem
                        {
                            ItemId = item.Id.ToString(),
                            Title = item.Title,
                            Url = item.ItemUrl.StartsWith("//") ? $"https:{item.ItemUrl}" : item.ItemUrl,
                            Status = item.Status,
                            CurrentPrice = item.CurrentPrice,
                            ConsultPrice = item.ConsultPrice,
                            Start = DateTimeOffset.FromUnixTimeMilliseconds(item.Start).DateTime,
                            End = DateTimeOffset.FromUnixTimeMilliseconds(item.End).DateTime,
                            ViewerCount = item.ViewerCount,
                            ApplyCount = item.ApplyCount,
                            PicUrl = item.PicUrl.StartsWith("//") ? $"https:{item.PicUrl}" : item.PicUrl
                        }).ToList();
                        log.AppendLine($"成功从 JSON 中解析到 {items.Count} 条数据。");


                        // ⬇️ 关键步骤 2: 抓取成功后，保存最新的 Cookies ⬇️
                        log.AppendLine("正在保存当前 Cookies 以备下次使用...");
                        var currentCookies = await context.CookiesAsync().ConfigureAwait(false);
                        var cookieJson = JsonSerializer.Serialize(currentCookies, new JsonSerializerOptions { WriteIndented = true });
                        File.WriteAllText(COOKIE_FILE_PATH, cookieJson);
                        log.AppendLine($"已成功将 {currentCookies.Count} 条 Cookies 保存到 {COOKIE_FILE_PATH}");


                        return new Tuple<List<AuctionItem>, string>(items, log.ToString());
                    }
                    catch (Exception ex)
                    {
                        log.AppendLine($"抓取过程中发生错误: {ex.Message}");
                        if (page != null)
                        {
                            log.AppendLine("已保存截图 error_screenshot.png 供调试。");
                            await page.ScreenshotAsync(new PageScreenshotOptions { Path = "error_screenshot.png" }).ConfigureAwait(false);
                        }
                        return new Tuple<List<AuctionItem>, string>(new List<AuctionItem>(), log.ToString());
                    }
                }
                catch (Exception ex)
                {
                    log.AppendLine($"Playwright 启动或页面创建失败: {ex.Message}");
                    return new Tuple<List<AuctionItem>, string>(new List<AuctionItem>(), log.ToString());
                }
                finally
                {
                    if (context != null) // 关闭 context 就会关闭浏览器
                    {
                        await context.CloseAsync().ConfigureAwait(false);
                        log.AppendLine("浏览器已关闭。");
                    }
                }
            }
        }

        // 辅助函数 (不变)
        private static Microsoft.Playwright.SameSiteAttribute ParseSameSite(string sameSite)
        {
            // ... (此函数内容不变) ...
            #region ParseSameSite
            if (sameSite == null) return Microsoft.Playwright.SameSiteAttribute.None;
            switch (sameSite.ToLower())
            {
                case "strict":
                    return Microsoft.Playwright.SameSiteAttribute.Strict;
                case "lax":
                    return Microsoft.Playwright.SameSiteAttribute.Lax;
                case "no_restriction":
                default:
                    return Microsoft.Playwright.SameSiteAttribute.None;
            }
            #endregion
        }
    }
}